#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
营业执照识别临时文件管理器
提供异常安全的临时文件创建、使用和清理机制
"""

import os
import tempfile
import logging
from contextlib import contextmanager
from typing import Optional, Generator, List
from pathlib import Path
import atexit
import weakref
import threading
from src.business_license_errors import (
    BusinessLicenseSystemError, 
    BusinessLicenseErrorType,
    business_license_error_handler
)


class TempFileManager:
    """临时文件管理器 - 确保异常安全的文件清理"""
    
    def __init__(self, logger: Optional[logging.Logger] = None):
        """
        初始化临时文件管理器
        
        Args:
            logger: 日志记录器
        """
        self.logger = logger or logging.getLogger(__name__)
        self._temp_files: List[str] = []
        self._lock = threading.Lock()
        
        # 注册程序退出时的清理函数
        atexit.register(self._cleanup_all_temp_files)
        
        # 使用弱引用避免循环引用
        self._weakref = weakref.ref(self, self._cleanup_callback)
    
    @staticmethod
    def _cleanup_callback(weak_ref):
        """弱引用回调函数，用于对象销毁时的清理"""
        # 这里不能访问实例方法，因为对象已经被销毁
        pass
    
    def create_temp_file(self, 
                        suffix: str = '.jpg',
                        prefix: str = 'business_license_',
                        dir: Optional[str] = None) -> str:
        """
        创建临时文件
        
        Args:
            suffix: 文件后缀
            prefix: 文件前缀
            dir: 临时文件目录，None表示使用系统默认
            
        Returns:
            str: 临时文件路径
            
        Raises:
            BusinessLicenseSystemError: 临时文件创建失败
        """
        try:
            # 创建临时文件
            temp_fd, temp_path = tempfile.mkstemp(
                suffix=suffix,
                prefix=prefix,
                dir=dir
            )
            
            # 关闭文件描述符，但保留文件
            os.close(temp_fd)
            
            # 记录临时文件路径
            with self._lock:
                self._temp_files.append(temp_path)
            
            self.logger.debug(f"创建临时文件: {temp_path}")
            return temp_path
            
        except OSError as e:
            error = BusinessLicenseSystemError(
                BusinessLicenseErrorType.SYSTEM_TEMP_FILE_CREATION_FAILED,
                f"临时文件创建失败: {str(e)}",
                {'suffix': suffix, 'prefix': prefix, 'dir': dir},
                e
            )
            raise error
        except Exception as e:
            error = BusinessLicenseSystemError(
                BusinessLicenseErrorType.SYSTEM_INTERNAL_ERROR,
                f"临时文件创建时发生未知错误: {str(e)}",
                {'suffix': suffix, 'prefix': prefix, 'dir': dir},
                e
            )
            raise error
    
    def cleanup_temp_file(self, file_path: str) -> bool:
        """
        清理指定的临时文件
        
        Args:
            file_path: 要清理的文件路径
            
        Returns:
            bool: 清理是否成功
        """
        if not file_path:
            return True
        
        try:
            if os.path.exists(file_path):
                os.unlink(file_path)
                self.logger.debug(f"成功清理临时文件: {file_path}")
            
            # 从记录中移除
            with self._lock:
                if file_path in self._temp_files:
                    self._temp_files.remove(file_path)
            
            return True
            
        except OSError as e:
            self.logger.warning(f"清理临时文件失败: {file_path}, 错误: {str(e)}")
            return False
        except Exception as e:
            self.logger.error(f"清理临时文件时发生未知错误: {file_path}, 错误: {str(e)}")
            return False
    
    def _cleanup_all_temp_files(self):
        """清理所有记录的临时文件"""
        with self._lock:
            temp_files_copy = self._temp_files.copy()
        
        cleaned_count = 0
        failed_count = 0
        
        for file_path in temp_files_copy:
            if self.cleanup_temp_file(file_path):
                cleaned_count += 1
            else:
                failed_count += 1
        
        if cleaned_count > 0 or failed_count > 0:
            self.logger.info(f"临时文件清理完成: 成功清理 {cleaned_count} 个文件, 失败 {failed_count} 个文件")
    
    def get_temp_files_count(self) -> int:
        """获取当前临时文件数量"""
        with self._lock:
            return len(self._temp_files)
    
    def get_temp_files_list(self) -> List[str]:
        """获取当前临时文件列表的副本"""
        with self._lock:
            return self._temp_files.copy()


@contextmanager
def safe_temp_file(suffix: str = '.jpg',
                  prefix: str = 'business_license_',
                  dir: Optional[str] = None,
                  manager: Optional[TempFileManager] = None) -> Generator[str, None, None]:
    """
    安全的临时文件上下文管理器
    
    Args:
        suffix: 文件后缀
        prefix: 文件前缀
        dir: 临时文件目录
        manager: 临时文件管理器实例
        
    Yields:
        str: 临时文件路径
        
    Example:
        with safe_temp_file('.jpg') as temp_path:
            # 使用临时文件
            with open(temp_path, 'wb') as f:
                f.write(image_data)
            # 文件会在退出时自动清理
    """
    temp_manager = manager or TempFileManager()
    temp_path = None
    
    try:
        # 创建临时文件
        temp_path = temp_manager.create_temp_file(suffix, prefix, dir)
        yield temp_path
        
    except Exception as e:
        # 记录异常但不重新抛出，让调用者处理
        logging.getLogger(__name__).error(f"临时文件使用过程中发生错误: {str(e)}")
        raise
        
    finally:
        # 确保清理临时文件
        if temp_path:
            temp_manager.cleanup_temp_file(temp_path)


@contextmanager
def safe_temp_file_from_upload(uploaded_file,
                              suffix: Optional[str] = None,
                              manager: Optional[TempFileManager] = None) -> Generator[str, None, None]:
    """
    从上传文件创建安全的临时文件
    
    Args:
        uploaded_file: Flask上传的文件对象
        suffix: 文件后缀，如果为None则从原文件名推断
        manager: 临时文件管理器实例
        
    Yields:
        str: 临时文件路径
        
    Raises:
        BusinessLicenseFileError: 文件保存失败
    """
    from src.business_license_errors import BusinessLicenseFileError, BusinessLicenseErrorType
    
    temp_manager = manager or TempFileManager()
    temp_path = None
    
    try:
        # 推断文件后缀
        if suffix is None:
            if uploaded_file.filename:
                file_ext = os.path.splitext(uploaded_file.filename.lower())[1]
                suffix = file_ext if file_ext else '.jpg'
            else:
                suffix = '.jpg'
        
        # 创建临时文件
        temp_path = temp_manager.create_temp_file(suffix=suffix)
        
        # 保存上传的文件
        try:
            uploaded_file.save(temp_path)
        except Exception as e:
            raise BusinessLicenseFileError(
                BusinessLicenseErrorType.FILE_READ_ERROR,
                f"保存上传文件失败: {str(e)}",
                {'filename': uploaded_file.filename, 'temp_path': temp_path},
                e
            )
        
        yield temp_path
        
    except Exception as e:
        if not isinstance(e, BusinessLicenseFileError):
            # 包装为业务异常
            raise BusinessLicenseSystemError(
                BusinessLicenseErrorType.SYSTEM_TEMP_FILE_CREATION_FAILED,
                f"处理上传文件时发生错误: {str(e)}",
                {'filename': getattr(uploaded_file, 'filename', 'unknown')},
                e
            )
        raise
        
    finally:
        # 确保清理临时文件
        if temp_path:
            temp_manager.cleanup_temp_file(temp_path)


@contextmanager
def safe_temp_file_from_url(url: str,
                           manager: Optional[TempFileManager] = None) -> Generator[str, None, None]:
    """
    从URL下载文件并创建安全的临时文件
    
    Args:
        url: 文件URL
        manager: 临时文件管理器实例
        
    Yields:
        str: 临时文件路径
        
    Raises:
        BusinessLicenseNetworkError: 网络下载失败
    """
    import requests
    from src.business_license_errors import BusinessLicenseNetworkError, BusinessLicenseErrorType
    
    temp_manager = manager or TempFileManager()
    temp_path = None
    
    try:
        # 验证URL协议
        if not (url.lower().startswith('http://') or url.lower().startswith('https://')):
            raise BusinessLicenseNetworkError(
                BusinessLicenseErrorType.NETWORK_PROTOCOL_UNSUPPORTED,
                f"不支持的协议: {url}",
                {'url': url}
            )
        
        # 推断文件后缀
        url_lower = url.lower()
        if url_lower.endswith('.pdf'):
            suffix = '.pdf'
        elif any(url_lower.endswith(ext) for ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']):
            suffix = os.path.splitext(url_lower)[1]
        else:
            suffix = '.jpg'  # 默认为jpg
        
        # 创建临时文件
        temp_path = temp_manager.create_temp_file(suffix=suffix)
        
        # 下载文件
        try:
            headers = {
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
            }
            
            response = requests.get(url, headers=headers, timeout=30, stream=True)
            response.raise_for_status()
            
            # 检查文件大小
            content_length = response.headers.get('Content-Length')
            if content_length and int(content_length) > 16 * 1024 * 1024:  # 16MB
                raise BusinessLicenseNetworkError(
                    BusinessLicenseErrorType.FILE_SIZE_EXCEEDED,
                    f"远程文件大小超过限制: {content_length} bytes",
                    {'url': url, 'file_size': content_length}
                )
            
            # 流式下载并写入临时文件
            downloaded_size = 0
            max_size = 16 * 1024 * 1024  # 16MB
            
            with open(temp_path, 'wb') as f:
                for chunk in response.iter_content(chunk_size=8192):
                    if chunk:
                        downloaded_size += len(chunk)
                        if downloaded_size > max_size:
                            raise BusinessLicenseNetworkError(
                                BusinessLicenseErrorType.FILE_SIZE_EXCEEDED,
                                f"下载文件大小超过限制: {downloaded_size} bytes",
                                {'url': url, 'downloaded_size': downloaded_size}
                            )
                        f.write(chunk)
            
        except requests.exceptions.Timeout as e:
            raise BusinessLicenseNetworkError(
                BusinessLicenseErrorType.NETWORK_DOWNLOAD_TIMEOUT,
                f"下载超时: {str(e)}",
                {'url': url},
                e
            )
        except requests.exceptions.ConnectionError as e:
            raise BusinessLicenseNetworkError(
                BusinessLicenseErrorType.NETWORK_CONNECTION_FAILED,
                f"网络连接失败: {str(e)}",
                {'url': url},
                e
            )
        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 404:
                error_type = BusinessLicenseErrorType.NETWORK_REMOTE_FILE_NOT_FOUND
                message = f"远程文件不存在: {str(e)}"
            else:
                error_type = BusinessLicenseErrorType.NETWORK_CONNECTION_FAILED
                message = f"HTTP错误: {str(e)}"
            
            raise BusinessLicenseNetworkError(error_type, message, {'url': url}, e)
        except Exception as e:
            raise BusinessLicenseNetworkError(
                BusinessLicenseErrorType.NETWORK_CONNECTION_FAILED,
                f"下载文件失败: {str(e)}",
                {'url': url},
                e
            )
        
        yield temp_path
        
    finally:
        # 确保清理临时文件
        if temp_path:
            temp_manager.cleanup_temp_file(temp_path)


# 全局临时文件管理器实例
global_temp_file_manager = TempFileManager()